package com.frog.lock.starter.core;

import com.frog.lock.starter.annotation.Klock;
import com.frog.lock.starter.annotation.KlockKey;
import com.frog.lock.starter.cache.V1.BaseCache;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.context.expression.MethodBasedEvaluationContext;
import org.springframework.core.DefaultParameterNameDiscoverer;
import org.springframework.core.ParameterNameDiscoverer;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;

import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;


public class BusinessKeyProvider {

    private ParameterNameDiscoverer nameDiscoverer = new DefaultParameterNameDiscoverer();

    private ExpressionParser parser = new SpelExpressionParser();


    /**
     * 解析注解中的lock name
     */
    public List<String> getKeyName(JoinPoint joinPoint, Klock klock) {
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        Method method = signature.getMethod();
        if (BaseCache.class.isAssignableFrom(method.getDeclaringClass()))
            return getCacheKeyName(joinPoint, method, klock);
        return getMostKeyName(joinPoint, method, klock);
    }


    /**
     * 普通处理锁名称
     */
    private List<String> getMostKeyName(JoinPoint joinPoint, Method method, Klock klock) {
        List<String> resList = new ArrayList<>();
        List<String> keyList = new ArrayList<>();
        List<String> definitionKeys = getSpelDefinitionKey(klock.keys(), method, joinPoint.getArgs());
        keyList.addAll(definitionKeys);
        List<String> parameterKeys = getParameterKey(method.getParameters(), joinPoint.getArgs());
        keyList.addAll(parameterKeys);
        resList.add(StringUtils.collectionToDelimitedString(keyList, "", "-", ""));
        return resList;
    }


    /**
     * 缓存冷热分离锁名称
     */
    private List<String> getCacheKeyName(JoinPoint joinPoint, Method method, Klock klock) {
        List<String> respList = new ArrayList<>();
        List<String> definitionKeys = getSpelDefinitionKey(klock.keys(), method, joinPoint.getArgs());
        String key = definitionKeys.get(0);

        Parameter[] parameters = method.getParameters();
        Object[] parameterValues = joinPoint.getArgs();
        Class<?>[] parameterTypes = method.getParameterTypes();

        for (int i = 0; i < parameters.length; ++i) {
            if (parameters[i].getAnnotation(KlockKey.class) == null) continue;
            KlockKey keyAnnotation = (KlockKey) parameters[i].getAnnotation(KlockKey.class);

            if (keyAnnotation.value().isEmpty()) {
                // 二维数组类型
                if (parameterTypes[i] == Object[][].class) {
                    Object[][] parameterValue = (Object[][]) parameterValues[i];
                    for (Object[] objects : parameterValue) {
                        Object[] array = Arrays.stream(objects).map(String::valueOf).toArray();
                        String format = MessageFormat.format(key, array);
                        respList.add(format);
                    }
                    continue;
                }
                // 一位数组类型
                if (parameterTypes[i] == Object[].class) {
                    Object[] parameterValue = (Object[]) parameterValues[i];
                    Object[] array = Arrays.stream(parameterValue).map(String::valueOf).toArray();
                    String format = MessageFormat.format(key, array);
                    respList.add(format);
                    continue;
                }
                // 其他基本数据类型
                String string = String.valueOf(parameterValues[i]);
                String format = MessageFormat.format(key, string);
                respList.add(format);

            } else {
                StandardEvaluationContext context = new StandardEvaluationContext(parameterValues[i]);
                Object[] key111 = (Object[]) this.parser.parseExpression(keyAnnotation.value()).getValue(context);
                Object[] array = Arrays.stream(key111).map(String::valueOf).toArray();
                String format = MessageFormat.format(key, array);
                respList.add(format);
            }
        }
        return respList;
    }


    /**
     * Spel获取 Klock-keys
     */
    private List<String> getSpelDefinitionKey(String[] definitionKeys, Method method, Object[] parameterValues) {
        List<String> definitionKeyList = new ArrayList<>();
        for (String definitionKey : definitionKeys) {
            if (!ObjectUtils.isEmpty(definitionKey)) {
                EvaluationContext context = new MethodBasedEvaluationContext(null, method, parameterValues, nameDiscoverer);
                Object objKey = parser.parseExpression(definitionKey).getValue(context);
                definitionKeyList.add(ObjectUtils.nullSafeToString(objKey));
            }
        }
        return definitionKeyList;
    }


    /**
     * 获取 @KlockKey注解值
     */
    private List<String> getParameterKey(Parameter[] parameters, Object[] parameterValues) {
        List<String> parameterKey = new ArrayList<>();
        for (int i = 0; i < parameters.length; i++) {
            if (parameters[i].getAnnotation(KlockKey.class) != null) {
                KlockKey keyAnnotation = parameters[i].getAnnotation(KlockKey.class);
                if (keyAnnotation.value().isEmpty()) {
                    Object parameterValue = parameterValues[i];
                    parameterKey.add(ObjectUtils.nullSafeToString(parameterValue));
                } else {
                    StandardEvaluationContext context = new StandardEvaluationContext(parameterValues[i]);
                    Object key = parser.parseExpression(keyAnnotation.value()).getValue(context);
                    parameterKey.add(ObjectUtils.nullSafeToString(key));
                }
            }
        }
        return parameterKey;
    }
}